﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Eventable
{
    public class EventProviderSpec : EventProvider, ISilentAccess, IProbeChanging
    {
        #region SilentAccess
        private class AccessWatch : IDisposable
        {
            EventProviderSpec Entity;
            public AccessWatch(EventProviderSpec entity)
            {
                this.Entity = entity;
                Entity._CountUpdate++;
            }
            public void Dispose()
            {
                Entity._CountUpdate--;
            }
        }

        private int _CountUpdate;
        public bool IsUpdate()
        {
            return _CountUpdate != 0;
        }

        /// <summary>
        /// Изменение характеристик без вызова событий
        /// </summary>
        /// <returns></returns>
        public IDisposable GetSilentAccess()
        {
            return new AccessWatch(this);
        }
        #endregion

        #region Constructors
        public EventProviderSpec()
            : this(null) { }

        public EventProviderSpec(object Owner)
            : base(Owner) { }
        #endregion

        public bool SetValueProperty<T>(string propName, T newValue, Action<T> setterNewValue, Func<T> getCurValue, T defValue)
        {
            if (newValue.IsDefault())
                newValue = defValue;
            return SetValueProperty<T>(propName, newValue, setterNewValue, getCurValue);
        }

        public bool SetValueProperty<T>(string propName, T newValue, Action<T> setterNewValue, Func<T> getCurValue)
        {
            var cur = getCurValue();
            if (IsUpdate() || NotifyEvent(propName + "Changing", cur, newValue))
            {
                setterNewValue(newValue);
                return TryAllNotifyEvent(propName + "Changed", cur, newValue);
            }
            return false;
        }

        public bool SetValueProperty<T>(string propName, T newValue, Action<T> setterNewValue, Func<T> getCurValue, Func<T> getDefValue)
        {
            if (newValue.IsDefault())
                newValue = getDefValue();
            return SetValueProperty<T>(propName, newValue, setterNewValue, getCurValue);
        }

        public bool SetSilentValueProperty<T>(Action<T> setterNewValue, string propName, T newValue, Func<T> getCurValue, T defValue)
        {
            if (newValue.IsDefault())
                newValue = defValue;
            if (IsUpdate())
            {
                var cur = getCurValue();
                setterNewValue(newValue);
                TryAllNotifyEvent(propName + "Changed", cur, getCurValue());
                return true;
            }
            else
                return false;
        }

        public bool ClearCollection<T>(string propName, ICollection<T> coll, Action<IEnumerable<T>> remove = null)
        {
            var added = new T[0];
            var removed = coll.ToArray();
            bool res = false;
            if (IsUpdate() || NotifyEvent(propName + "Changing", RulesArgs.Collection.ForChange(coll, added, removed)))
            {
                if (res = remove != null)
                    remove(removed);
                res = res && TryAllNotifyEvent(propName + "Changed", RulesArgs.Collection.ForChange(coll, added, removed));
            }
            return res;
        }

        public bool RemoveItemFromCollection<T>(string propName, ICollection<T> coll, T removeValue, Action<T> remove = null)
        {
            var added = new T[0];
            var removed = new T[] { removeValue };
            bool res = false;
            if (IsUpdate() || NotifyEvent(propName + "Changing", RulesArgs.Collection.ForChange(coll, added, removed)))
            {
                if (res = remove != null)
                    remove(removeValue);
                res = res && TryAllNotifyEvent(propName + "Changed", RulesArgs.Collection.ForChange(coll, added, removed));
            }
            return res;
        }

        public bool AddItemToCollection<T>(string propName, ICollection<T> coll, T addValue, Action<T> add = null)
        {            
            var added = new T[] { addValue };
            var removed = new T[0];
            bool res = false;
            if (IsUpdate() || NotifyEvent(propName + "Changing", RulesArgs.Collection.ForChange(coll, added, removed)))
            {
                if (res = add != null)
                    add(addValue);
                res = res && TryAllNotifyEvent(propName + "Changed", RulesArgs.Collection.ForChange(coll, added, removed));
            }
            return res;
        }

        public bool ChangeCollection<T>(string propName, ICollection<T> coll, IEnumerable<T> addValue, Action<IEnumerable<T>> add = null, IEnumerable<T> removeValue = null, Action<IEnumerable<T>> remove = null)
        {
            addValue = addValue ?? new T[0];
            removeValue = removeValue ?? new T[0];
            bool res = false;
            if (IsUpdate() || NotifyEvent(propName + "Changing", coll, addValue, removeValue))
            {
                if (remove == null)
                {
                    if (coll != null)
                        foreach (var item in removeValue)
                            res = res | coll.Remove(item);
                }
                else
                {
                    remove(removeValue);
                    res = true;
                }

                if (add == null)
                {
                    if (coll != null)
                        foreach (var item in addValue)
                        {
                            coll.Add(item);
                            res = true;
                        }
                }
                else
                {
                    add(addValue);
                    res = true;
                }

                res = res && TryAllNotifyEvent(propName + "Changed", coll, addValue, removeValue);
            }
            return res;
        }

        #region IProbeChanging
        public bool CanChangeValue<T>(string propName, T newValue)
        {
            return TryNotifyEvent(propName + "Changing", RulesArgs.ForChangeValue(PropertyInfoExtension.GetValue<T>(Owner, propName), newValue));
        }

        public Exception GetErrorForChangeValue<T>(string propName, T newValue)
        {
            Exception ex;
            TryNotifyEvent(propName + "Changing", out ex, RulesArgs.ForChangeValue(PropertyInfoExtension.GetValue<T>(Owner, propName), newValue));
            return ex;
        }

        public bool GetErrorForChangeValue<T>(string propName, T newValue, out Exception[] errors)
        {
            return TryAllNotifyEvent(propName + "Changing", out errors, RulesArgs.ForChangeValue(PropertyInfoExtension.GetValue<T>(Owner, propName), newValue));
        }
        #endregion

    }
}
